package com.lj.sys.aspect;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.json.JSONUtil;
import com.lj.common_web.annotation.SysLog;
import com.lj.common_web.utils.ServletUtil;
import com.lj.sys.entity.SysLogEntity;
import com.lj.sys.service.SysLogService;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 系统日志aop
 *
 * @author luojing
 * @since 2025/4/18 17:30
 */
@Aspect
@Component
@Slf4j
public class SysLogAspect {

    @Resource
    private SysLogService sysLogService;

    @Pointcut("@annotation(com.lj.common_web.annotation.SysLog)")
    private void getLogPointcut() {
    }

    @Around("getLogPointcut()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        long startTime = System.currentTimeMillis();
        try {
            // 执行方法
            Object result = point.proceed();
            succeed(point, result, System.currentTimeMillis() - startTime);
            return result;
        } catch (Throwable e) {
            // 出现错误只是记录日志，继续抛出错误由调用测处理
            fail(point, e, System.currentTimeMillis() - startTime);
            throw e;
        }
    }

    private void succeed(ProceedingJoinPoint point, Object result, long duration) {
        SysLogEntity sysLogEntity = getBaseInfo(point, duration);
        // 异步保存日志
        ThreadUtil.execute(() -> {
            sysLogEntity.setSucceed(true);
            sysLogEntity.setResultJson(getResultJson(result));
            sysLogService.save(sysLogEntity);
        });
    }

    private void fail(ProceedingJoinPoint point, Throwable e, long duration) {
        SysLogEntity sysLogEntity = getBaseInfo(point, duration);
        // 异步保存日志
        ThreadUtil.execute(() -> {
            sysLogEntity.setSucceed(false);
            sysLogEntity.setError(ExceptionUtil.stacktraceToString(e));
            sysLogService.save(sysLogEntity);
        });
    }


    private SysLogEntity getBaseInfo(ProceedingJoinPoint point, long duration) {
        // 执行成功开始记录
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        SysLog sysLog = method.getAnnotation(SysLog.class);

        SysLogEntity sysLogEntity = new SysLogEntity();
        sysLogEntity.setLogName(sysLog.value());
        HttpServletRequest request = ServletUtil.getRequest();
        sysLogEntity.setIp(ServletUtil.getClientIP(request));
        sysLogEntity.setUrl(request.getRequestURI());
        sysLogEntity.setMethod(request.getMethod());
        sysLogEntity.setMethodName(point.getTarget().getClass().getName() + "#" + signature.getName());
        sysLogEntity.setParamsJson(getParamsJson(point));
        sysLogEntity.setDuration(duration);
        sysLogEntity.setCreateUser(getUserId());
        return sysLogEntity;
    }

    private String getParamsJson(ProceedingJoinPoint point) {
        Object[] args = point.getArgs();
        List<Object> paramList = new ArrayList<>();
        if (ArrayUtil.isNotEmpty(args)) {
            for (Object paramValue : args) {
                if (excludeParam(paramValue)) {
                    continue;
                }
                paramList.add(paramValue);
            }
        }
        try {
            return JSONUtil.toJsonStr(paramList);
        } catch (Exception e) {
            log.warn("无法获取参数:{}", paramList, e);
            // 上面json的时候可能会失败,所以为了防止不影响正常执行这里进行了 try-catch
            return "获取参数失败:" + e.getMessage();
        }
    }

    /**
     * 有些参数无法json序列化,需要提前排除
     * 例如HttpServletRequest，HttpServletResponse，MultipartFile
     *
     * @param paramValue 参数值
     * @return 是否是需要排除的参数
     */
    private boolean excludeParam(Object paramValue) {
        return paramValue instanceof HttpServletRequest ||
                paramValue instanceof HttpServletResponse ||
                paramValue instanceof MultipartFile;
    }

    private String getResultJson(Object result) {
        try {
            return JSONUtil.toJsonStr(result);
        } catch (Exception e) {
            return "获取结果失败:" + e;
        }
    }

    /**
     * 获取用户id
     */
    private Long getUserId() {
        try {
            return StpUtil.getLoginIdAsLong();
        } catch (Exception e) {
            return -1L;
        }
    }

}
